//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Audio/AudioCommon.h"

#ifdef PLATFORM_WINDOWS

namespace Engine
{
namespace Audio
{
	
#	define BASS_CALL( _func )				{ _func;  ::Engine::Audio::BassAudio::_CheckBassErrors( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ); }
#	define BASS_CALL_B( _func, _res )		{ _func;  _res &= ::Engine::Audio::BassAudio::_CheckBassErrors( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ); }
#	define BASS_CALL_R( _func, _ret )		{ _func;  if ( not ::Engine::Audio::BassAudio::_CheckBassErrors( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ) ) return _ret; }


	//
	// Bass Audio
	//

	class BassAudio : public Resource
	{
	// variables
	private:
		RFilePtr				_file;
		winapi::HSTREAM			_audioStream;
		uint					_duration;
		EPlayingState::type		_state;


	// methods
	public:
		BassAudio (const EngineSubSystemsRef ess);
		~BassAudio ();
		
		bool IsCreated () const;

		bool Load (uni_c_string filename);
		bool Load (const RFilePtr &file, EAudioFormat::type format);

		void Play ()							{ return SetState( EPlayingState::PLAY ); }
		void Stop ()							{ return SetState( EPlayingState::STOP ); }
		void Pause ()							{ return SetState( EPlayingState::PAUSE ); }

		void SetState (EPlayingState::type state);
		EPlayingState::type GetState ();

		void  SetLooping (bool enabled);
		bool  IsLooping () const;

		void  SetVolume (float volume);
		float GetVolume () const;
		
		void  SetPositionMillisec (uint pos);
		void  SetPosition (float percent)					{ return SetPositionMillisec( uint( percent * _duration + 0.5f ) ); }
		uint  GetPosition ()					const;
		uint  GetDurationMillisec ()			const		{ return _duration; }
		float GetDuration ()					const		{ return float(_duration) * 0.001f; }

		//void  SetRate (float rate);
		//float GetRate () const;


		// Resources //
		virtual bool SwapData (const ResourcePtr &) override;
		
		virtual bool IsValid () const override;


		// Errors //
		static bool _CheckBassErrors (uni_c_string bassCall, uni_c_string func, uni_c_string file, int line);

	protected:
		virtual void _OnFinished () = 0;
		
		virtual void _Destroy ();

	private:
		bool _OnCreate ();

		bool _LoadFile (uni_c_string filename);
		bool _LoadUrl (uni_c_string filename);

		uint   _ByteToMillisec (uint64 value) const;
		uint64 _MillisecToByte (uint value) const;

		static void CALLBACK _OnFinishedCallback (winapi::HSYNC handle, winapi::DWORD channel, winapi::DWORD data, void *user);
		
		static void				CALLBACK _FileCloseProc (void *user);
		static winapi::QWORD	CALLBACK _FileLenProc  (void *user);
		static winapi::DWORD	CALLBACK _FileReadProc (void *buf, winapi::DWORD length, void *user);
		static winapi::BOOL		CALLBACK _FileSeekProc (winapi::QWORD offset, void *user);
	};


	
/*
=================================================
	constructor
=================================================
*/
	inline BassAudio::BassAudio (const EngineSubSystemsRef ess) :
		Resource( EResource::AUDIO_SAMPLE, ess ),
		_audioStream(0),	_state(EPlayingState::STOP),
		_duration(0)
	{
	}
	
/*
=================================================
	destructor
=================================================
*/
	inline BassAudio::~BassAudio ()
	{
	}
		
/*
=================================================
	IsCreated
=================================================
*/
	inline bool BassAudio::IsCreated () const
	{
		return _audioStream != 0;
	}
	
/*
=================================================
	SetState
=================================================
*/
	inline void BassAudio::SetState (EPlayingState::type state)
	{
		using namespace winapi;

		switch ( state )
		{
			case EPlayingState::PLAY :
				BASS_CALL( BASS_ChannelPlay( _audioStream, _state == EPlayingState::STOP ) );
				break;

			case EPlayingState::STOP :
				BASS_CALL( BASS_ChannelStop( _audioStream ) );
				break;

			case EPlayingState::PAUSE :
				BASS_CALL( BASS_ChannelPause( _audioStream ) );
				break;
		}

		_state = state;
	}
	
/*
=================================================
	GetState
=================================================
*/
	inline EPlayingState::type BassAudio::GetState ()
	{
		using namespace winapi;

		DWORD res = BASS_ChannelIsActive( _audioStream );

		switch ( res )
		{
			case BASS_ACTIVE_STOPPED :	_state = EPlayingState::STOP;	break;
			case BASS_ACTIVE_PLAYING :	_state = EPlayingState::PLAY;	break;

			case BASS_ACTIVE_STALLED :	// no data to playing
			case BASS_ACTIVE_PAUSED :	_state = EPlayingState::PAUSE;	break;
		}

		return _state;
	}
	
/*
=================================================
	SetLooping
=================================================
*/
	inline void BassAudio::SetLooping (bool enabled)
	{
		using namespace winapi;

		BASS_CALL( BASS_ChannelFlags( _audioStream, enabled ? BASS_SAMPLE_LOOP : 0, BASS_SAMPLE_LOOP ) );
	}
	
/*
=================================================
	IsLooping
=================================================
*/
	inline bool BassAudio::IsLooping () const
	{
		using namespace winapi;
		DWORD	flags;

		BASS_CALL( flags = BASS_ChannelFlags( _audioStream, 0, 0 ) );
		return EnumCmp( flags, BASS_SAMPLE_LOOP );
	}
	
/*
=================================================
	SetVolume
=================================================
*/
	inline void  BassAudio::SetVolume (float volume)
	{
		using namespace winapi;

		volume = Clamp( volume, 0.0f, 1.0f );
		BASS_CALL( BASS_ChannelSetAttribute( _audioStream, BASS_ATTRIB_VOL, volume ) );
	}
	
/*
=================================================
	GetVolume
=================================================
*/
	inline float BassAudio::GetVolume () const
	{
		using namespace winapi;
		
		float volume = 1.0f;
        BASS_ChannelGetAttribute( _audioStream, BASS_ATTRIB_VOL, &volume );
		return volume;
	}

/*
=================================================
	_OnFinishedCallback
=================================================
*/
	inline void CALLBACK BassAudio::_OnFinishedCallback (winapi::HSYNC handle, winapi::DWORD channel, winapi::DWORD data, void *user)
	{
		BassAudio *	audio = (BassAudio *) user;
		audio->_OnFinished();
	}
	
/*
=================================================
	SetPositionMillisec
=================================================
*/
	inline void BassAudio::SetPositionMillisec (uint pos)
	{
		using namespace winapi;

		BASS_CALL( BASS_ChannelSetPosition( _audioStream, _MillisecToByte( pos ), BASS_POS_BYTE ) );
	}
	
/*
=================================================
	GetPosition
=================================================
*/
	inline uint BassAudio::GetPosition () const
	{
		using namespace winapi;
		
		QWORD  pos;

		BASS_CALL( pos = BASS_ChannelGetPosition( _audioStream, BASS_POS_BYTE ) );
		return _ByteToMillisec( pos );
	}
	
/*
=================================================
	SetRate
=================================================
*
	inline void BassAudio::SetRate (float rate)
	{
		using namespace winapi;

		BASS_CALL( BASS_ChannelSetAttribute( _audioStream, BASS_ATTRIB_SRC, rate ) );
	}
	
/*
=================================================
	GetRate
=================================================
*
	inline float BassAudio::GetRate () const
	{
		using namespace winapi;

		float	rate = 0.0f;
		BASS_CALL( BASS_ChannelGetAttribute( _audioStream, BASS_ATTRIB_SRC, &rate ) );

		return rate;
	}
	
/*
=================================================
	_ByteToMillisec
=================================================
*/
	inline uint BassAudio::_ByteToMillisec (uint64 value) const
	{
		using namespace winapi;

		double	time;

		BASS_CALL( time = BASS_ChannelBytes2Seconds( _audioStream, value ) );
		return Round<uint>( time * 1000.0 );
	}
		
/*
=================================================
	_MillisecToByte
=================================================
*/
	inline uint64 BassAudio::_MillisecToByte (uint value) const
	{
		using namespace winapi;

		QWORD	byte_value;

		BASS_CALL( byte_value = BASS_ChannelSeconds2Bytes( _audioStream, value * 0.001 ) );
		return byte_value;
	}

/*
=================================================
	_OnCreate
=================================================
*/
	inline bool BassAudio::_OnCreate ()
	{
		using namespace winapi;

		QWORD	len;
		BASS_CALL( len = BASS_ChannelGetLength( _audioStream, BASS_POS_BYTE ) );

		_memSize = (usize) len;

		_duration = _ByteToMillisec( len );

		BASS_CALL( BASS_ChannelSetSync( _audioStream, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, &_OnFinishedCallback, this ) );
		return true;
	}

/*
=================================================
	_Destroy
=================================================
*/
	inline void BassAudio::_Destroy ()
	{
		using namespace winapi;

		if ( _audioStream != 0 )
		{
			BASS_CALL( BASS_StreamFree( _audioStream ) );
		}

		_file			= 0;
		_memSize		= 0;

		_audioStream	= 0;
		_duration		= 0;
		_state			= EPlayingState::STOP;
	}
	
/*
=================================================
	Load
=================================================
*/
	inline bool BassAudio::Load (uni_c_string filename)
	{
		_Destroy();

		if ( ESS()->GetFileSystem()->IsFileExist( filename ) )
		{
			CHECK_ERR( _LoadFile( filename ) );
		}
		else
		{
			CHECK_ERR( _LoadUrl( filename ) );
		}
		return true;
	}
	
/*
=================================================
	Load
=================================================
*/
	inline bool BassAudio::Load (const RFilePtr &file, EAudioFormat::type)
	{
		CHECK_ERR( file.IsNotNull() );

		using namespace winapi;

		// TODO: use crypt file
		_file = File::SubRFile::New( file, file->Pos(), file->RemainingSize() );

		BASS_FILEPROCS	procs = { &_FileCloseProc, &_FileLenProc, &_FileReadProc, &_FileSeekProc };
		
		BASS_CALL_R( _audioStream = BASS_StreamCreateFileUser( STREAMFILE_NOBUFFER, 0, &procs, this ), false );
		return _OnCreate();
	}

/*
=================================================
	_LoadFile
=================================================
*/
	inline bool BassAudio::_LoadFile (uni_c_string filename)
	{
		using namespace winapi;

		BASS_CALL_R( _audioStream = BASS_StreamCreateFile( FALSE, filename.cstr(), 0, 0, 0 ), false );
		return _OnCreate();
	}
	
/*
=================================================
	_LoadUrl
=================================================
*/
	inline bool BassAudio::_LoadUrl (uni_c_string filename)
	{
		using namespace winapi;

		CHECK_ERR( filename.CmpPrefix( "http" ) or filename.CmpPrefix( "https" ) );

		BASS_CALL_R( _audioStream = BASS_StreamCreateURL( filename.cstr(), 0, 0, null, 0 ), false );
		return _OnCreate();
	}
	
/*
=================================================
	SwapData
=================================================
*/
	inline bool BassAudio::SwapData (const ResourcePtr &res)
	{
		CHECK_ERR( Resource::SwapData( res ) );
		
		BassAudio *	audio = res.ToPtr< BassAudio >();

		_swap( this->_audioStream,	audio->_audioStream );
		_swap( this->_state,		audio->_state );
		_swap( this->_duration,		audio->_duration );
		_swap( this->_file,			audio->_file );

		return true;
	}
	
/*
=================================================
	SwapData
=================================================
*/
	inline bool BassAudio::IsValid () const
	{
		return _audioStream != 0;
	}

/*
=================================================
	_FileCloseProc
=================================================
*/
	inline void  CALLBACK BassAudio::_FileCloseProc (void *user)
	{
		((BassAudio *) user)->_file = null;
	}
	
/*
=================================================
	_FileLenProc
=================================================
*/
	inline winapi::QWORD  CALLBACK BassAudio::_FileLenProc  (void *user)
	{
		return ((BassAudio *) user)->_file->Size();
	}
	
/*
=================================================
	_FileReadProc
=================================================
*/
	inline winapi::DWORD  CALLBACK BassAudio::_FileReadProc (void *buf, winapi::DWORD length, void *user)
	{
		return ((BassAudio *) user)->_file->ReadBuf( buf, length );
	}
	
/*
=================================================
	_FileSeekProc
=================================================
*/
	inline winapi::BOOL  CALLBACK BassAudio::_FileSeekProc (winapi::QWORD offset, void *user)
	{
		return ((BassAudio *) user)->_file->SeekSet( (isize) offset );
	}

/*
=================================================
	_CheckBassErrors
=================================================
*/
	inline bool BassAudio::_CheckBassErrors (uni_c_string bassCall, uni_c_string func, uni_c_string file, int line)
	{
		using namespace winapi;

		int	err = BASS_ErrorGetCode();

		if ( err == BASS_OK )
			return true;

		string	msg("BASS error: ");

#		define CASE_BASS_ERR( _type )	case _type :	msg << #_type;	break;

		switch ( err )
		{
			CASE_BASS_ERR( BASS_ERROR_MEM )
			CASE_BASS_ERR( BASS_ERROR_FILEOPEN )
			CASE_BASS_ERR( BASS_ERROR_DRIVER )
			CASE_BASS_ERR( BASS_ERROR_BUFLOST )
			CASE_BASS_ERR( BASS_ERROR_HANDLE )
			CASE_BASS_ERR( BASS_ERROR_FORMAT )
			CASE_BASS_ERR( BASS_ERROR_POSITION )
			CASE_BASS_ERR( BASS_ERROR_INIT )
			CASE_BASS_ERR( BASS_ERROR_START )
			CASE_BASS_ERR( BASS_ERROR_ALREADY )
			CASE_BASS_ERR( BASS_ERROR_NOCHAN )
			CASE_BASS_ERR( BASS_ERROR_ILLTYPE )
			CASE_BASS_ERR( BASS_ERROR_ILLPARAM )
			CASE_BASS_ERR( BASS_ERROR_NO3D )
			CASE_BASS_ERR( BASS_ERROR_NOEAX )
			CASE_BASS_ERR( BASS_ERROR_DEVICE )
			CASE_BASS_ERR( BASS_ERROR_NOPLAY )
			CASE_BASS_ERR( BASS_ERROR_FREQ )
			CASE_BASS_ERR( BASS_ERROR_NOTFILE )
			CASE_BASS_ERR( BASS_ERROR_NOHW )
			CASE_BASS_ERR( BASS_ERROR_EMPTY )
			CASE_BASS_ERR( BASS_ERROR_NONET )
			CASE_BASS_ERR( BASS_ERROR_CREATE )
			CASE_BASS_ERR( BASS_ERROR_NOFX )
			CASE_BASS_ERR( BASS_ERROR_NOTAVAIL )
			CASE_BASS_ERR( BASS_ERROR_DECODE )
			CASE_BASS_ERR( BASS_ERROR_DX )
			CASE_BASS_ERR( BASS_ERROR_TIMEOUT )
			CASE_BASS_ERR( BASS_ERROR_FILEFORM )
			CASE_BASS_ERR( BASS_ERROR_SPEAKER )
			CASE_BASS_ERR( BASS_ERROR_VERSION )  
			CASE_BASS_ERR( BASS_ERROR_CODEC )
			CASE_BASS_ERR( BASS_ERROR_ENDED ) 
			CASE_BASS_ERR( BASS_ERROR_BUSY )
			CASE_BASS_ERR( BASS_ERROR_UNKNOWN )
			default :	msg << "code 0x" << string().FormatI( err, 16 );
		}

#		undef CASE_BASS_ERR
		
		msg << ", in " << bassCall << ", function " << func << ", file: \"" << file
			<< "\", line: " << string().FormatI(line, 10);

		LOG( msg.cstr(), ELog::ERROR );
		return false;
	}


}	// Audio
}	// Engine

#endif	// PLATFORM_WINDOWS